package pathfinding;

import java.awt.geom.Point2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Random;

import ownserver.ServerShip;
import robotutils.data.DoubleUtils;

import base.Geometry;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.util.ScalableHashMap;
import com.sun.sgs.app.util.ScalableList;

public class DriveObstacles implements ManagedObject, Serializable, Task {

	private static final long serialVersionUID = 1L;
	public static final int PERIOD_MS = 5000;

	final private static double chancePerSecond = 1/20.0;
	
	//ManagedReference<ScalableList<ServerShip>> shipsRef;
	ArrayList<ManagedReference<ServerShip>> shipRefs;
	GridCell origo;
	int rangeLimit;
	Random random;
	ManagedReference<ScalableHashMap<ServerShip, GridCell>> goalsRef;
	
	public DriveObstacles(ScalableList<ServerShip> shipsList, GridCell origo, int rangeLimit)
	{
		DataManager dataManager = AppContext.getDataManager();
		dataManager.markForUpdate(this);
		shipRefs = new ArrayList<ManagedReference<ServerShip>>();
		for (ServerShip s: shipsList) {
			shipRefs.add(dataManager.createReference(s));
		}
		
		this.origo = origo;
		this.rangeLimit = rangeLimit;
		random = new Random();
		ScalableHashMap<ServerShip, GridCell> goals = new ScalableHashMap<ServerShip, GridCell>();
		goalsRef = dataManager.createReference(goals);
	}
	
	@Override
	public void run() throws Exception {
		ScalableHashMap<ServerShip, GridCell> goals = goalsRef.get();
		
		for (ManagedReference<ServerShip> s : shipRefs)
		{
			drive(s.get(), goals);
		}
	}
	
	private void drive(ServerShip s, ScalableHashMap<ServerShip, GridCell> goals) {
		GridCell goal = goals.get(s);
		if (goal == null) {
			goal = getTarget();
			goals.put(s, goal);
		}
		else if (needsUpdate())
		{
			goal = getTarget();
			goals.put(s, goal);
		}
		
		drive(s, goal);
	}
	
	private void drive(ServerShip ship, GridCell target)
	{
		double closeEnoughSq = GridGeometry.gridSize / 3.0;
		closeEnoughSq *= closeEnoughSq;
		
		Point2D.Double tgtPos = GridGeometry.getCenter(target);
		Point2D.Double shipPos = ship.getPos();
		double distSq = Geometry.getDistanceSquared(shipPos, tgtPos);
		
		if (distSq <= closeEnoughSq) {
			updateVelocity(ship, 0);
			return;
		}
		
		// if the ship goes at max speed when getting near to it's target it might overshoot
		double distInPeriod = PERIOD_MS / 1000.0 * ship.getMaxVelocity();
		double distInPeriodSq = distInPeriod * distInPeriod;
		double velocityModifier = distSq / distInPeriodSq;
		if (velocityModifier > 1.0) {
			velocityModifier = 1.0;
		}
		
		updateHeading(ship, Geometry.getHeading(shipPos.x, shipPos.y, tgtPos.x, tgtPos.y));
		updateVelocity(ship, ship.getMaxVelocity() * velocityModifier);
	}
	
	private void updateVelocity(ServerShip s, double newVelocity)
	{
		if (DoubleUtils.equals(newVelocity, s.getVelocity()) == false) {
			s.setVelocity(newVelocity);
		}
	}
	
	private void updateHeading(ServerShip s, double newHeading) {
		if (DoubleUtils.equals(newHeading, s.getHeading()) == false) {
			s.setHeading(newHeading);
		}
	}
	
	private GridCell getTarget()
	{
		int x = origo.x + (random.nextInt() % (rangeLimit * 2)) - rangeLimit;
		int y = origo.y + (random.nextInt() % (rangeLimit * 2)) - rangeLimit;
		return new GridCell(x, y);
	}
	
	private boolean needsUpdate() {
		double chance = PERIOD_MS / 1000.0 * chancePerSecond;
		
		return (random.nextDouble() < chance);
	}

}
